#pragma once

#include <iostream>
#include <string>
#include <unordered_map>
#include <functional>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "err.hpp"
#include "RingQueue.hpp"
#include "LockGuard.hpp"
#include "Thread.hpp"

namespace nt_server
{
    // 端口号默认值
    const uint16_t default_port = 8888;

    class UdpServer
    {
    public:
        // 构造
        UdpServer(uint16_t port = default_port)
            :port_(port)
        {
            // 初始化互斥锁
            pthread_mutex_init(&mtx_, nullptr);

            // 创建线程
            // 注意：因为类内成员有隐含的 this 指针，需要借助 bind 固定该参数
            producer_ = new Thread(1, std::bind(&UdpServer::RecvMessage, this));
            consumer_ = new Thread(2, std::bind(&UdpServer::BroadcastMessage, this));
        }
        // 析构
        ~UdpServer()
        {
            // 等待线程运行结束
            producer_->join();
            consumer_->join();

            // 销毁互斥锁
            pthread_mutex_destroy(&mtx_);

            // 释放对象
            delete producer_;
            delete consumer_;
        } 

        // 初始化服务器
        void StartServer()
        {
            // 1.创建套接字
            sock_ = socket(AF_INET, SOCK_DGRAM, 0);

            if(sock_ == -1)
            {
                std::cout << "Create Socket Fail: " << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }

            // 创建成功
            std::cout << "Create Success Socket: " << sock_ << std::endl;

            // 2.绑定IP地址和端口号
            struct sockaddr_in local;
            bzero(&local, sizeof(local)); // 置0

            // 填充字段
            local.sin_family = AF_INET; // 设置为网络通信（PF_INET 也行）
            local.sin_port = htons(port_); // 主机序列转为网络序列
            // local.sin_addr.s_addr = inet_addr(ip_.c_str()); // 点分十进制转为短整数，再将主机序列转为网络序列
            local.sin_addr.s_addr = INADDR_ANY; // 绑定任何可用IP地址

            // 绑定IP地址和端口号
            if(bind(sock_, (const sockaddr*)&local, sizeof(local)))
            {
                std::cout << "Bind IP&&Port Fail: " << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }

            // 绑定成功
            std::cout << "Bind IP&&Port Success" << std::endl;

            // 启动线程
            producer_->run();
            consumer_->run();
        }

        // 接收消息
        void RecvMessage()
        {
            // 服务器是不断运行的，所以需要使用一个 while(true) 死循环
            char buff[1024]; // 缓冲区
            while(true)
            {
                // 1. 接收消息
                struct sockaddr_in peer; // 客户端结构体
                socklen_t len = sizeof(peer); // 客户端结构体大小

                // 传入 sizeof(buff) - 1 表示当前传输的是字符串，预留一个位置存储 '\0'
                // 传入 0 表示当前是阻塞式读取
                ssize_t n = recvfrom(sock_, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&peer, &len);

                if(n > 0)
                    buff[n] = '\0';
                else
                    continue; // 继续读取

                // 2.处理数据
                std::string clientIp = inet_ntoa(peer.sin_addr); // 获取IP地址
                uint16_t clientPort = ntohs(peer.sin_port); // 获取端口号
                printf("Server get message from [%s:%d]$ %s\n",clientIp.c_str(), clientPort, buff);

                // 3.判断是否该添加用户
                std::string user = clientIp + "-" + std::to_string(clientPort);

                {
                    // 需要加锁保护
                    LockGuard lockguard(&mtx_);
                    if(userTable_.count(user) == 0)
                        userTable_[user] = peer; // 首次出现，需要添加
                }

                // 4.将消息添加至环形队列
                std::string msg = "[" + clientIp + ":" + std::to_string(clientPort) + "] say# " + buff;
                rq_.Push(msg);
            }
        }

        // 广播消息
        void BroadcastMessage()
        {
            while(true)
            {
                // 1.从环形队列中获取消息
                std::string msg;
                rq_.Pop(&msg);

                // 2.将消息发给用户
                std::vector<sockaddr_in> arr;

                {
                    // 从哈希表中读取信息时，需要保护
                    LockGuard lockguard(&mtx_);
                    for(auto &user : userTable_)
                        arr.push_back(user.second);
                }

                for(auto &addr : arr)
                {
                    // 发送消息
                    sendto(sock_, msg.c_str(), msg.size(), 0, (const sockaddr*)&addr, sizeof(addr));
                }
            }
        }

    private:
        int sock_; // 套接字
        uint16_t port_; // 端口号
        ccc::RingQueue<std::string> rq_; // 环形队列
        std::unordered_map<std::string, struct sockaddr_in> userTable_; // <用户标识符, sockaddr_in 结构体>
        pthread_mutex_t mtx_; // 互斥锁，保护哈希表
        Thread* producer_; // 生产者
        Thread* consumer_; // 消费者
    };
}
